using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Timers;

namespace Campus
{
    public enum PlayerRace
    {
        Sciences,
        Humanities
    };

    public class Player
    {
        #region Fields

        public List<BaseEntity> Destroyed = new List<BaseEntity>();
        public List<BaseEntity> Added = new List<BaseEntity>();

        private Vector2 m_startPosition = Vector2.Zero;
        public Vector2 StartPosition
        {
            get { return m_startPosition; }
            set { m_startPosition = value; }
        }

        private int m_teamNumber = 0;
        public int TeamNumber
        {
            get { return m_teamNumber; }
            set { m_teamNumber = value; }
        }

        private Sprite m_pointer;
        public Sprite Pointer
        {
            get { return m_pointer; }
            set { m_pointer = value; }
        }

        /// <summary>
        /// Whether or not the computer AI should be active
        /// </summary>
        private bool m_isComputerPlayer = false;
        public bool IsComputerPlayer
        {
            get { return m_isComputerPlayer; }
            set { m_isComputerPlayer = value; }
        }

        /// <summary>
        /// the speed the screen scrolls at for the mouse
        /// </summary>
        private float m_scrollSpeed = 300;
        public float ScrollSpeed
        {
            get { return m_scrollSpeed; }
            set { m_scrollSpeed = value; }
        }

        /// <summary>
        /// Selected Units and structures
        /// </summary>
        private List<BaseEntity> m_selected = new List<BaseEntity>();
        public List<BaseEntity> Selected
        {
            get { return m_selected; }
            set { m_selected = value; }
        }

        private BaseEntity m_currentUnit;
        public BaseEntity CurrentUnit
        {
            get { return m_currentUnit; }
            set { m_currentUnit = value; }
        }

        /// <summary>
        /// units controlled by this player
        /// </summary>
        private List<Unit> m_units = new List<Unit>();
        public List<Unit> Units
        {
            get { return m_units; }
            set { m_units = value; }
        }

        /// <summary>
        /// Structures owned by this player
        /// </summary>
        private List<Structure> m_structures = new List<Structure>();
        public List<Structure> Structures
        {
            get { return m_structures; }
            set { m_structures = value; }
        }

        /// <summary>
        /// Position of this players mouse
        /// </summary>
        private Vector2 m_mousePosition = new Vector2(0, 0);
        public Vector2 MousePosition
        {
            get { return m_mousePosition; }
        }

        /// <summary>
        /// The area that this player is currently seeing
        /// </summary>
        public Vector2 ScreenPosition = new Vector2(0, 0);

        /// <summary>
        /// Color of the players team
        /// </summary>
        private Color m_teamColor = Color.White;
        public Color TeamColor
        {
            get { return m_teamColor; }
            set { m_teamColor = value; }
        }

        /// <summary>
        /// Total available amount of Food
        /// </summary>
        private int m_maxFood = 0;
        public int MaxFood
        {
            get { return m_maxFood; }
            set { m_maxFood = value; }
        }

        /// <summary>
        /// Amount of food used
        /// </summary>
        private int m_usedFood = 0;
        public int UsedFood
        {
            get { return m_usedFood; }
            set { m_usedFood = value; }
        }

        /// <summary>
        /// Amount of available Research
        /// </summary>
        private int m_research = 50;
        public int Research
        {
            get { return m_research; }
            set { m_research = value; }
        }

        /// <summary>
        /// Amount of available money
        /// </summary>
        private int m_money = 50;
        public int Money
        {
            get { return m_money; }
            set { m_money = value; }
        }

        /// <summary>
        /// The number of workers that this player will start with.
        /// </summary>
        public int StartingWorkers
        {
            get { return m_startingWorkers; }
        }
        private int m_startingWorkers = 3;

        private HUD m_hud;
        public HUD HUD
        {
            get { return m_hud; }
            set { m_hud = value; }
        }

        private Minimap m_minimap;
        public Minimap Minimap
        {
            get { return m_minimap; }
            set { m_minimap = value; }
        }

        /// <summary>
        /// The Level that this Player is currently playing on.
        /// </summary>
        public Level CurrentLevel
        {
            get { return m_currentLevel; }
            set { m_currentLevel = value; }
        }
        private Level m_currentLevel = null;

        /// <summary>
        /// The "race" that this player is: Sciences or Humanities.
        /// </summary>
        public PlayerRace Race
        {
            get { return m_race; }
        }
        private PlayerRace m_race;

        /// <summary>
        /// This is passed in by Campus to ensure that the Player can load their own content.
        /// </summary>
        private ContentManager m_contentManager;

        /// <summary>
        /// A list of the Sprites that are visible to the player.  Kept up to date by Level's Draw function.
        /// </summary>
        public List<Sprite> VisibleSprites
        {
            get;
            set;
        }

        /// <summary>
        /// A list of strings that are displayed to the player in a portion of the HUD.
        /// </summary>
        public List<string> HUDMessages
        {
            get;
            private set;
        }

        private bool m_isPlacingStructure = false;
        public bool IsPlacingStructure {
            get { return m_isPlacingStructure; }
            set { m_isPlacingStructure = value; }
        }

        public bool IsValidPlace { get; set; }

        private BuildRequirements.Buildings m_placingStructure = BuildRequirements.Buildings.SchoolCafeteria;
        public BuildRequirements.Buildings PlacingStructure {
            get { return m_placingStructure; }
            set { m_placingStructure = value; }
        }
        private MouseState m_oldMouseState;
        private MouseState m_currentMouseState;

        #endregion

        #region Constructors

        public Player(ContentManager contentManager, PlayerRace race, HUD hud)
        {
            m_contentManager = contentManager;
            m_race = race;
            m_hud = hud;

            VisibleSprites = new List<Sprite>();
            HUDMessages = new List<string>();

            m_pointer = new Sprite((Texture2D)null, Vector2.Zero);
            m_pointer.Size = new Vector2(1, 1);
        }

        #endregion

        #region General Methods

        #region Tiny Methods

        public void AddMoney(int addedMoney)
        {
            this.m_money += addedMoney;
        }

        public void AddResearch(int addedResearch)
        {
            this.m_research += addedResearch;
        }

        public void AddMaxFood(int addedToMax)
        {
            m_maxFood += addedToMax;
        }

        public void AddUsedFood(int addedToUsed)
        {
            m_usedFood += addedToUsed;
        }

        public void ScrollRight(GameTime gameTime)
        {
            ScreenPosition.X += (float)gameTime.ElapsedGameTime.TotalSeconds * m_scrollSpeed;
        }

        public void ScrollLeft(GameTime gameTime)
        {
            ScreenPosition.X -= (float)gameTime.ElapsedGameTime.TotalSeconds * m_scrollSpeed;
        }

        public void ScrollDown(GameTime gameTime)
        {
            ScreenPosition.Y += (float)gameTime.ElapsedGameTime.TotalSeconds * m_scrollSpeed;
        }

        public void ScrollUp(GameTime gameTime)
        {
            ScreenPosition.Y -= (float)gameTime.ElapsedGameTime.TotalSeconds * m_scrollSpeed;
        }

        public void ClearEntities()
        {
            Units.Clear();
            Structures.Clear();
        }

        public void ClearResources()
        {
            Money = 0;
            Research = 0;
            MaxFood = 0;
            UsedFood = 0;
        }

        public void ResetPlayer()
        {
            ClearEntities();
            ClearResources();
        }

        #endregion

        private float OldTime = 0;
        public void Update(GameTime gameTime)
        {
            Vector2 cursorAbsolutePosition = ScreenPosition + m_mousePosition;

            // Update the minimap if this Player is on a Level.
            if (m_currentLevel != null)
                m_minimap.Update();

            if (!m_isComputerPlayer)
            {
                // Update the HUD.
                m_hud.Update(this);

                m_currentMouseState = Mouse.GetState();
                m_mousePosition = new Vector2(m_currentMouseState.X, m_currentMouseState.Y);
                
                m_pointer.Position = cursorAbsolutePosition;
                m_pointer.UpdateBoundingSphere();
                 

                for (int i = 0; i < m_selected.Count; i++)
                {
                    if (m_selected[i] != null && m_selected[i].CurrentHP <= 0)
                    {
                        m_selected.RemoveAt(i);
                    }
                }

                if ((float)gameTime.TotalGameTime.TotalSeconds - OldTime >= 5)
                {

                    OldTime = (float)gameTime.TotalGameTime.TotalSeconds;
                    for (int i = 0; i < m_structures.Count; i++)
                    {
                        m_structures[i].Update(this);
                    }
                }

                if (m_currentMouseState.RightButton != ButtonState.Pressed && m_oldMouseState.RightButton == ButtonState.Pressed)
                {
                    if (m_selected.Count > 0)
                    {
                        if (m_selected[0] is Unit)
                        {
                            for (int i = 0; i < m_selected.Count; i++)
                            {
                                BaseEntity b = m_selected[i];

                                if (b is Unit && b.TeamNumber == this.TeamNumber)
                                {
                                    Unit selectedUnit = (Unit)b;
                                    selectedUnit.Stop();

                                    MoveAbility move = (MoveAbility)selectedUnit.GetMoveAbility().Clone();
                                    move.FinalDestination = cursorAbsolutePosition;
                                    selectedUnit.FrontQueueAbility(move);
                                }
                                else
                                {
                                    b.IsSelected = false;
                                    m_selected.Remove(b);
                                }
                            }

                            foreach (BaseEntity b in m_currentLevel.BaseEntities)
                            {
                                if (b.TeamNumber != TeamNumber &&
                                    m_pointer.Position.X >= b.Position.X - b.Origin.X &&
                                    m_pointer.Position.X <= b.Position.X - b.Origin.X + b.Size.X &&
                                    m_pointer.Position.Y >= b.Position.Y - b.Origin.Y &&
                                    m_pointer.Position.Y <= b.Position.Y - b.Origin.Y + b.Size.Y)
                                {
                                    if (m_selected[0] is Unit)
                                    {
                                        foreach (BaseEntity u in m_selected)
                                        {
                                            Unit attacker = (Unit)u;
                                            if (attacker.GetAttackAbility() == null)
                                            {
                                                continue;
                                            }
                                            AttackAbility ab = (AttackAbility)attacker.GetAttackAbility().Clone();
                                            ab.Target = b;
                                            attacker.SetCurrentAbility(ab);
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            foreach (BaseEntity b in m_selected)
                            {
                                b.IsSelected = false;
                            }
                            m_selected = new List<BaseEntity>();
                        }
                    }
                }
                m_oldMouseState = m_currentMouseState;
            }
            else
            {
                // this player is an enemy update the boss
                foreach (BaseEntity b in m_currentLevel.BaseEntities)
                {
                    if (b.BoundingSphere.Intersects(Units[0].AttackBoundingBox)) ;
                }
            }

        }
        
        public void Draw(SpriteBatch spriteBatch)
        {
            // Draw the HUD and minimap.
            m_hud.Draw(spriteBatch);
            if (m_currentLevel != null)
                m_minimap.Draw(spriteBatch);

            // If we're placing a structure, we need to make sure we're drawing the "placement" texture.
            if (m_isPlacingStructure && m_currentLevel != null)
            {
                Texture2D structureTexture;
                Texture2D structureTeamColorTexture;
                Vector2 structureOrigin;
                List<Vector2> tilesOccupied;
                Color tintColor = Color.LightPink;
                IsValidPlace = false;

                structureTexture = BuildRequirements.GetStructureBuildingTexture(m_placingStructure);
                structureTeamColorTexture = BuildRequirements.GetStructureTeamColorTexture(m_placingStructure);
                structureOrigin = BuildRequirements.GetStructureOrigin(m_placingStructure);
                tilesOccupied = BuildRequirements.GetStructureTilesOccupied(m_placingStructure);
                    

                MouseState ms = Mouse.GetState();
                Vector2 gridPosition;
                if (m_currentLevel.GetGridPositionForPoint(new Vector2(ms.X + ScreenPosition.X, ms.Y + ScreenPosition.Y), out gridPosition))
                {
                    if (m_currentLevel.TilesAreAvailable(gridPosition, tilesOccupied)) {
                        tintColor = Color.LightGreen;
                        IsValidPlace = true;
                    }

                    Vector2 position = m_currentLevel.GetOriginOfTile((int)gridPosition.X, (int)gridPosition.Y);
                    Rectangle destinationRect = new Rectangle((int)(position.X - ScreenPosition.X), (int)(position.Y - ScreenPosition.Y), (int)structureTexture.Width, (int)structureTexture.Height);

                    spriteBatch.Begin();
                    spriteBatch.Draw(structureTexture, destinationRect, null, tintColor, 0.0f, structureOrigin, SpriteEffects.None, 0.0f);
                    spriteBatch.Draw(structureTeamColorTexture, destinationRect, null, this.TeamColor, 0.0f, structureOrigin, SpriteEffects.None, 0.0f);
                    spriteBatch.End();
                }
            }
        }

        /// <summary>
        /// Does all the necessary changes to Player in order to change the Level.
        /// </summary>
        /// <param name="newLevel">Level object this player is moving to</param>
        /// <param name="levelSizeInPixels">The size</param>
        public void SetLevel(Level newLevel)
        {
            Vector2 levelSizeInPixels = newLevel.PixelsInLevel;
            m_currentLevel = newLevel;

            // Generate the new minimap.
            InformationOverlay minimapArea = m_hud.GetInformationOverlay("leftEdgeBottom");
            // Get the location of the dark box in the edge (it has a +11, +10 offset from the corner of the texture).
            Vector2 minimapLocation = new Vector2(minimapArea.Location.X + 11, minimapArea.Location.Y + 10);
            // Hard code the size of that box in the HUD.
            // TODO: If we do any HUD scaling, need to change the hard-coded minimap size.
            Vector2 minimapSize = new Vector2(182, 136);

            // DEBUG
            //minimapLocation = Vector2.Zero;
            //minimapSize = new Vector2(800, 600);
            // DEBUG

            m_minimap = new Minimap(this, minimapLocation, minimapSize, levelSizeInPixels, m_contentManager);

            CreateHUDMessage("Minimap Loaded!", 5);
            CreateHUDMessage("This is a really long message to the player. I forgot that capital letters will mess up wordwrap :S", 10);
            CreateHUDMessage("Lifespans: 5 seconds, 10 seconds, 15 seconds.", 15);
        }

        /// <summary>
        /// Creates a new message to be displayed to this Player via the HUD.
        /// </summary>
        /// <param name="message">The string to be displayed</param>
        /// <param name="lifetime">The length of time that the message will remain on screen (in seconds)</param>
        public void CreateHUDMessage(string message, int lifetimeInSeconds)
        {
            HUDMessages.Add(message);

            double lifetimeInMillis = lifetimeInSeconds * 1000;
            Timer currentMessageTimer = new Timer(lifetimeInMillis);
            currentMessageTimer.AutoReset = false;
            currentMessageTimer.Elapsed += new ElapsedEventHandler(delegate(object o, ElapsedEventArgs e)
            {
                HUDMessages.Remove(message);
                currentMessageTimer.Dispose();
            });
            currentMessageTimer.Start();
        }

        #region Level/Player Interface Functions

        /// <summary>
        /// Using the variables set by a Level object, set the player up at their starting position.
        /// </summary>
        public void SpawnWithWorkers()
        {
            //// Keep in mind that it takes a tile (row, column), which is NOT (x, y), it's (y, x)
            //CurrentLevel.AddBaseEntityToTile(new ExperimentalLaboratory(this, this.CurrentLevel), (int)m_startPosition.Y + 4, (int)m_startPosition.X + 4);
            //CurrentLevel.AddBaseEntityToTile(new Scholarship(this, this.CurrentLevel), (int)m_startPosition.Y + 8, (int)m_startPosition.X + 4);
            //CurrentLevel.AddBaseEntityToTile(new SchoolCafeteria(this, this.CurrentLevel), (int)m_startPosition.Y + 12, (int)m_startPosition.X + 4);
            //CurrentLevel.AddBaseEntityToTile(new CleanRoom(this, this.CurrentLevel), (int)m_startPosition.Y + 16, (int)m_startPosition.X + 4);
            //CurrentLevel.AddBaseEntityToTile(new MothersBasement(this, this.CurrentLevel), (int)m_startPosition.Y + 20, (int)m_startPosition.X + 4);
            //CurrentLevel.AddBaseEntityToTile(new Dispatch(this, this.CurrentLevel), (int)m_startPosition.Y + 20, (int)m_startPosition.X);
            //CurrentLevel.AddBaseEntityToTile(new InterdimensionalPortal(this, this.CurrentLevel), (int)m_startPosition.Y + 12, (int)m_startPosition.X);
            //CurrentLevel.AddBaseEntityToTile(new GatewayToHell(this, this.CurrentLevel), (int)m_startPosition.Y + 8, (int)m_startPosition.X);
            //CurrentLevel.AddBaseEntityToTile(new DownByTheRiver(this, this.CurrentLevel), (int)m_startPosition.Y + 10, (int)m_startPosition.X + 4);
            
            Structure s = BuildRequirements.BuildStructure(BuildRequirements.Buildings.CollegeOfEngineering, this);
            CurrentLevel.AddBaseEntityToTile(s, (int)m_startPosition.Y, (int)m_startPosition.X);
            // Create workers for them as well
            for (int i = 0; i < m_startingWorkers; i++)
            {
                MechanicalEngineer worker = new MechanicalEngineer(Vector2.Zero, this, m_currentLevel);
                m_currentLevel.AddBaseEntityToTile(worker, (int)m_startPosition.Y + i, (int)m_startPosition.X - i);
                worker.Stop();
            }
            this.AddUsedFood(m_startingWorkers);
            //// Create a test physicist.
            //Physicist phys = new Physicist(Vector2.Zero, this, m_currentLevel);
            //m_currentLevel.AddBaseEntityToTile(phys, (int)m_startPosition.Y, (int)m_startPosition.X);
            //phys.Stop();

            ////Create a test shuttle
            //CampusShuttle shuttle = new CampusShuttle(Vector2.Zero, this, m_currentLevel);
            //m_currentLevel.AddBaseEntityToTile(shuttle, (int)m_startPosition.Y, (int)m_startPosition.X);
            //shuttle.Stop();

            ////Create a test Robot
            //Robot robot = new Robot(Vector2.Zero, this, m_currentLevel);
            //m_currentLevel.AddBaseEntityToTile(robot, (int)m_startPosition.Y, (int)m_startPosition.X);
            //robot.Stop();

            ////Create a test Biologist
            //Biologist biologist = new Biologist(Vector2.Zero, this, m_currentLevel);
            //m_currentLevel.AddBaseEntityToTile(biologist, (int)m_startPosition.Y, (int)m_startPosition.X);
            //biologist.Stop();

            ////create a test CS
            //ComputerScientist cs = new ComputerScientist(Vector2.Zero, this, m_currentLevel);
            //m_currentLevel.AddBaseEntityToTile(cs, (int)m_startPosition.Y, (int)m_startPosition.X);
            //cs.Stop();

            //////create a test arch
            ////Architect arch = new Architect(Vector2.Zero, this, m_currentLevel);
            ////m_currentLevel.AddBaseEntityToTile(arch, (int)m_startPosition.Y, (int)m_startPosition.X);
            ////arch.Stop();
        }

        #endregion

        #endregion

    }
}
